#!/usr/bin/python

# Copyright (c) 2017, Eike Frost <ei@kefro.st>
# Copyright (c) 2021, Christophe Gilles <christophe.gilles54@gmail.com>
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later

from __future__ import annotations

DOCUMENTATION = r"""
module: keycloak_realm

short_description: Allows administration of Keycloak realm using Keycloak API

version_added: 3.0.0

description:
  - This module allows the administration of Keycloak realm using the Keycloak REST API. It requires access to the REST API
    using OpenID Connect; the user connecting and the realm being used must have the requisite access rights. In a default
    Keycloak installation, admin-cli and an admin user would work, as would a separate realm definition with the scope tailored
    to your needs and a user having the expected roles.
  - The names of module options are snake_cased versions of the camelCase ones found in the Keycloak API and its documentation
    at U(https://www.keycloak.org/docs-api/8.0/rest-api/index.html). Aliases are provided so camelCased versions can be used
    as well.
  - The Keycloak API does not always sanity check inputs, for example you can set SAML-specific settings on an OpenID Connect
    client for instance and also the other way around. B(Be careful). If you do not specify a setting, usually a sensible
    default is chosen.
attributes:
  check_mode:
    support: full
  diff_mode:
    support: full
  action_group:
    version_added: 10.2.0

options:
  state:
    description:
      - State of the realm.
      - On V(present), the realm is created (or updated if it exists already).
      - On V(absent), the realm is removed if it exists.
    choices: ['present', 'absent']
    default: 'present'
    type: str

  id:
    description:
      - The realm to create.
    type: str
  realm:
    description:
      - The realm name.
    type: str
  access_code_lifespan:
    description:
      - The realm access code lifespan.
    aliases:
      - accessCodeLifespan
    type: int
  access_code_lifespan_login:
    description:
      - The realm access code lifespan login.
    aliases:
      - accessCodeLifespanLogin
    type: int
  access_code_lifespan_user_action:
    description:
      - The realm access code lifespan user action.
    aliases:
      - accessCodeLifespanUserAction
    type: int
  access_token_lifespan:
    description:
      - The realm access token lifespan.
    aliases:
      - accessTokenLifespan
    type: int
  access_token_lifespan_for_implicit_flow:
    description:
      - The realm access token lifespan for implicit flow.
    aliases:
      - accessTokenLifespanForImplicitFlow
    type: int
  account_theme:
    description:
      - The realm account theme.
    aliases:
      - accountTheme
    type: str
  action_token_generated_by_admin_lifespan:
    description:
      - The realm action token generated by admin lifespan.
    aliases:
      - actionTokenGeneratedByAdminLifespan
    type: int
  action_token_generated_by_user_lifespan:
    description:
      - The realm action token generated by user lifespan.
    aliases:
      - actionTokenGeneratedByUserLifespan
    type: int
  admin_events_details_enabled:
    description:
      - The realm admin events details enabled.
    aliases:
      - adminEventsDetailsEnabled
    type: bool
  admin_events_enabled:
    description:
      - The realm admin events enabled.
    aliases:
      - adminEventsEnabled
    type: bool
  admin_permissions_enabled:
    description:
      - The realm admin permissions enabled.
    aliases:
      - adminPermissionsEnabled
    type: bool
    version_added: 12.0.0
  admin_theme:
    description:
      - The realm admin theme.
    aliases:
      - adminTheme
    type: str
  attributes:
    description:
      - The realm attributes.
    type: dict
  browser_flow:
    description:
      - The realm browser flow.
    aliases:
      - browserFlow
    type: str
  browser_security_headers:
    description:
      - The realm browser security headers.
    aliases:
      - browserSecurityHeaders
    type: dict
  brute_force_protected:
    description:
      - The realm brute force protected.
    aliases:
      - bruteForceProtected
    type: bool
  brute_force_strategy:
    description:
      - The realm brute force strategy.
    aliases:
      - bruteForceStrategy
    choices: ['LINEAR', 'MULTIPLE']
    type: str
    version_added: 11.2.0
  client_authentication_flow:
    description:
      - The realm client authentication flow.
    aliases:
      - clientAuthenticationFlow
    type: str
  client_scope_mappings:
    description:
      - The realm client scope mappings.
    aliases:
      - clientScopeMappings
    type: dict
  default_default_client_scopes:
    description:
      - The realm default default client scopes.
    aliases:
      - defaultDefaultClientScopes
    type: list
    elements: str
  default_groups:
    description:
      - The realm default groups.
    aliases:
      - defaultGroups
    type: list
    elements: str
  default_locale:
    description:
      - The realm default locale.
    aliases:
      - defaultLocale
    type: str
  default_optional_client_scopes:
    description:
      - The realm default optional client scopes.
    aliases:
      - defaultOptionalClientScopes
    type: list
    elements: str
  default_roles:
    description:
      - The realm default roles.
    aliases:
      - defaultRoles
    type: list
    elements: str
  default_signature_algorithm:
    description:
      - The realm default signature algorithm.
    aliases:
      - defaultSignatureAlgorithm
    type: str
  direct_grant_flow:
    description:
      - The realm direct grant flow.
    aliases:
      - directGrantFlow
    type: str
  display_name:
    description:
      - The realm display name.
    aliases:
      - displayName
    type: str
  display_name_html:
    description:
      - The realm display name HTML.
    aliases:
      - displayNameHtml
    type: str
  docker_authentication_flow:
    description:
      - The realm docker authentication flow.
    aliases:
      - dockerAuthenticationFlow
    type: str
  duplicate_emails_allowed:
    description:
      - The realm duplicate emails allowed option.
    aliases:
      - duplicateEmailsAllowed
    type: bool
  edit_username_allowed:
    description:
      - The realm edit username allowed option.
    aliases:
      - editUsernameAllowed
    type: bool
  email_theme:
    description:
      - The realm email theme.
    aliases:
      - emailTheme
    type: str
  enabled:
    description:
      - The realm enabled option.
    type: bool
  enabled_event_types:
    description:
      - The realm enabled event types.
    aliases:
      - enabledEventTypes
    type: list
    elements: str
  events_enabled:
    description:
      - Enables or disables login events for this realm.
    aliases:
      - eventsEnabled
    type: bool
    version_added: 3.6.0
  events_expiration:
    description:
      - The realm events expiration.
    aliases:
      - eventsExpiration
    type: int
  events_listeners:
    description:
      - The realm events listeners.
    aliases:
      - eventsListeners
    type: list
    elements: str
  failure_factor:
    description:
      - The realm failure factor.
    aliases:
      - failureFactor
    type: int
  internationalization_enabled:
    description:
      - The realm internationalization enabled option.
    aliases:
      - internationalizationEnabled
    type: bool
  login_theme:
    description:
      - The realm login theme.
    aliases:
      - loginTheme
    type: str
  login_with_email_allowed:
    description:
      - The realm login with email allowed option.
    aliases:
      - loginWithEmailAllowed
    type: bool
  max_delta_time_seconds:
    description:
      - The realm max delta time in seconds.
    aliases:
      - maxDeltaTimeSeconds
    type: int
  max_failure_wait_seconds:
    description:
      - The realm max failure wait in seconds.
    aliases:
      - maxFailureWaitSeconds
    type: int
  max_temporary_lockouts:
    description:
      - The realm max temporary lockouts.
    aliases:
      - maxTemporaryLockouts
    type: int
    version_added: 11.2.0
  minimum_quick_login_wait_seconds:
    description:
      - The realm minimum quick login wait in seconds.
    aliases:
      - minimumQuickLoginWaitSeconds
    type: int
  not_before:
    description:
      - The realm not before.
    aliases:
      - notBefore
    type: int
  offline_session_idle_timeout:
    description:
      - The realm offline session idle timeout.
    aliases:
      - offlineSessionIdleTimeout
    type: int
  offline_session_max_lifespan:
    description:
      - The realm offline session max lifespan.
    aliases:
      - offlineSessionMaxLifespan
    type: int
  offline_session_max_lifespan_enabled:
    description:
      - The realm offline session max lifespan enabled option.
    aliases:
      - offlineSessionMaxLifespanEnabled
    type: bool
  otp_policy_algorithm:
    description:
      - The realm otp policy algorithm.
    aliases:
      - otpPolicyAlgorithm
    type: str
  otp_policy_digits:
    description:
      - The realm otp policy digits.
    aliases:
      - otpPolicyDigits
    type: int
  otp_policy_initial_counter:
    description:
      - The realm otp policy initial counter.
    aliases:
      - otpPolicyInitialCounter
    type: int
  otp_policy_look_ahead_window:
    description:
      - The realm otp policy look ahead window.
    aliases:
      - otpPolicyLookAheadWindow
    type: int
  otp_policy_period:
    description:
      - The realm otp policy period.
    aliases:
      - otpPolicyPeriod
    type: int
  otp_policy_type:
    description:
      - The realm otp policy type.
    aliases:
      - otpPolicyType
    type: str
  otp_supported_applications:
    description:
      - The realm otp supported applications.
    aliases:
      - otpSupportedApplications
    type: list
    elements: str
  password_policy:
    description:
      - The realm password policy.
    aliases:
      - passwordPolicy
    type: str
  organizations_enabled:
    description:
      - Enables support for experimental organization feature.
    aliases:
      - organizationsEnabled
    type: bool
    version_added: 10.0.0
  permanent_lockout:
    description:
      - The realm permanent lockout.
    aliases:
      - permanentLockout
    type: bool
  quick_login_check_milli_seconds:
    description:
      - The realm quick login check in milliseconds.
    aliases:
      - quickLoginCheckMilliSeconds
    type: int
  refresh_token_max_reuse:
    description:
      - The realm refresh token max reuse.
    aliases:
      - refreshTokenMaxReuse
    type: int
  registration_allowed:
    description:
      - The realm registration allowed option.
    aliases:
      - registrationAllowed
    type: bool
  registration_email_as_username:
    description:
      - The realm registration email as username option.
    aliases:
      - registrationEmailAsUsername
    type: bool
  registration_flow:
    description:
      - The realm registration flow.
    aliases:
      - registrationFlow
    type: str
  remember_me:
    description:
      - The realm remember me option.
    aliases:
      - rememberMe
    type: bool
  reset_credentials_flow:
    description:
      - The realm reset credentials flow.
    aliases:
      - resetCredentialsFlow
    type: str
  reset_password_allowed:
    description:
      - The realm reset password allowed option.
    aliases:
      - resetPasswordAllowed
    type: bool
  revoke_refresh_token:
    description:
      - The realm revoke refresh token option.
    aliases:
      - revokeRefreshToken
    type: bool
  smtp_server:
    description:
      - The realm smtp server.
    aliases:
      - smtpServer
    type: dict
  ssl_required:
    description:
      - The realm ssl required option.
    choices: ['all', 'external', 'none']
    aliases:
      - sslRequired
    type: str
  sso_session_idle_timeout:
    description:
      - The realm sso session idle timeout.
    aliases:
      - ssoSessionIdleTimeout
    type: int
  sso_session_idle_timeout_remember_me:
    description:
      - The realm sso session idle timeout remember me.
    aliases:
      - ssoSessionIdleTimeoutRememberMe
    type: int
  sso_session_max_lifespan:
    description:
      - The realm sso session max lifespan.
    aliases:
      - ssoSessionMaxLifespan
    type: int
  sso_session_max_lifespan_remember_me:
    description:
      - The realm sso session max lifespan remember me.
    aliases:
      - ssoSessionMaxLifespanRememberMe
    type: int
  supported_locales:
    description:
      - The realm supported locales.
    aliases:
      - supportedLocales
    type: list
    elements: str
  user_managed_access_allowed:
    description:
      - The realm user managed access allowed option.
    aliases:
      - userManagedAccessAllowed
    type: bool
  verify_email:
    description:
      - The realm verify email option.
    aliases:
      - verifyEmail
    type: bool
  wait_increment_seconds:
    description:
      - The realm wait increment in seconds.
    aliases:
      - waitIncrementSeconds
    type: int
  client_session_idle_timeout:
    description:
      - All Clients inherit from this setting, time a session is allowed to be idle before it expires.
    aliases:
      - clientSessionIdleTimeout
    type: int
    version_added: 11.2.0
  client_session_max_lifespan:
    description:
      - All Clients inherit from this setting, max time before a session is expired.
    aliases:
      - clientSessionMaxLifespan
    type: int
    version_added: 11.2.0
  client_offline_session_idle_timeout:
    description:
      - All Clients inherit from this setting, time an offline session is allowed to be idle before it expires.
    aliases:
      - clientOfflineSessionIdleTimeout
    type: int
    version_added: 11.2.0
  client_offline_session_max_lifespan:
    description:
      - All Clients inherit from this setting, max time before an offline session is expired regardless of activity.
    aliases:
      - clientOfflineSessionMaxLifespan
    type: int
    version_added: 11.2.0
  oauth2_device_code_lifespan:
    description:
      - Max time before the device code and user code are expired.
    aliases:
      - oauth2DeviceCodeLifespan
    type: int
    version_added: 11.2.0
  oauth2_device_polling_interval:
    description:
      - The minimum amount of time in seconds that the client should wait between polling requests to the token endpoint.
    aliases:
      - oauth2DevicePollingInterval
    type: int
    version_added: 11.2.0
  web_authn_policy_rp_entity_name:
    description:
      - WebAuthn Relying Party Entity Name.
    aliases:
      - webAuthnPolicyRpEntityName
    type: str
    version_added: 11.3.0
  web_authn_policy_signature_algorithms:
    description:
      - List of acceptable WebAuthn signature algorithms.
    aliases:
      - webAuthnPolicySignatureAlgorithms
    type: list
    version_added: 11.3.0
    elements: str
  web_authn_policy_rp_id:
    description:
      - WebAuthn Relying Party ID (domain). Empty string means use request host.
    aliases:
      - webAuthnPolicyRpId
    type: str
    version_added: 11.3.0
  web_authn_policy_attestation_conveyance_preference:
    description:
      - Attestation conveyance preference for WebAuthn.
    aliases:
      - webAuthnPolicyAttestationConveyancePreference
    type: str
    version_added: 11.3.0
  web_authn_policy_authenticator_attachment:
    description:
      - Authenticator attachment preference for WebAuthn authenticators.
    aliases:
      - webAuthnPolicyAuthenticatorAttachment
    type: str
    version_added: 11.3.0
  web_authn_policy_require_resident_key:
    description:
      - Whether resident keys are required for WebAuthn (Yes/No/not specified).
    aliases:
      - webAuthnPolicyRequireResidentKey
    type: str
    version_added: 11.3.0
  web_authn_policy_user_verification_requirement:
    description:
      - User verification requirement for WebAuthn.
    aliases:
      - webAuthnPolicyUserVerificationRequirement
    type: str
    version_added: 11.3.0
  web_authn_policy_create_timeout:
    description:
      - Timeout for WebAuthn credential creation (ms).
    aliases:
      - webAuthnPolicyCreateTimeout
    type: int
    version_added: 11.3.0
  web_authn_policy_avoid_same_authenticator_register:
    description:
      - Avoid registering the same authenticator multiple times.
    aliases:
      - webAuthnPolicyAvoidSameAuthenticatorRegister
    type: bool
    version_added: 11.3.0
  web_authn_policy_acceptable_aaguids:
    description:
      - List of acceptable AAGUIDs for WebAuthn authenticators.
    aliases:
      - webAuthnPolicyAcceptableAaguids
    type: list
    version_added: 11.3.0
    elements: str
  web_authn_policy_extra_origins:
    description:
      - Additional acceptable origins for WebAuthn requests.
    aliases:
      - webAuthnPolicyExtraOrigins
    type: list
    version_added: 11.3.0
    elements: str
  web_authn_policy_passwordless_rp_entity_name:
    description:
      - WebAuthn Passwordless Relying Party Entity Name.
    aliases:
      - webAuthnPolicyPasswordlessRpEntityName
    type: str
    version_added: 11.3.0
  web_authn_policy_passwordless_signature_algorithms:
    description:
      - List of acceptable WebAuthn signature algorithms for passwordless.
    aliases:
      - webAuthnPolicyPasswordlessSignatureAlgorithms
    type: list
    version_added: 11.3.0
    elements: str
  web_authn_policy_passwordless_rp_id:
    description:
      - WebAuthn Passwordless Relying Party ID (domain).
    aliases:
      - webAuthnPolicyPasswordlessRpId
    type: str
    version_added: 11.3.0
  web_authn_policy_passwordless_attestation_conveyance_preference:
    description:
      - Attestation conveyance preference for WebAuthn passwordless.
    aliases:
      - webAuthnPolicyPasswordlessAttestationConveyancePreference
    type: str
    version_added: 11.3.0
  web_authn_policy_passwordless_authenticator_attachment:
    description:
      - Authenticator attachment for WebAuthn passwordless.
    aliases:
      - webAuthnPolicyPasswordlessAuthenticatorAttachment
    type: str
    version_added: 11.3.0
  web_authn_policy_passwordless_require_resident_key:
    description:
      - Whether resident keys are required for WebAuthn passwordless (V(Yes)/V(No)/V(not specified)).
    aliases:
      - webAuthnPolicyPasswordlessRequireResidentKey
    type: str
    version_added: 11.3.0
  web_authn_policy_passwordless_user_verification_requirement:
    description:
      - User verification requirement for WebAuthn passwordless.
    aliases:
      - webAuthnPolicyPasswordlessUserVerificationRequirement
    type: str
    version_added: 11.3.0
  web_authn_policy_passwordless_create_timeout:
    description:
      - Timeout for WebAuthn passwordless credential creation (ms).
    aliases:
      - webAuthnPolicyPasswordlessCreateTimeout
    type: int
    version_added: 11.3.0
  web_authn_policy_passwordless_avoid_same_authenticator_register:
    description:
      - Avoid registering the same authenticator multiple times for passwordless.
    aliases:
      - webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister
    type: bool
    version_added: 11.3.0
  web_authn_policy_passwordless_acceptable_aaguids:
    description:
      - List of acceptable AAGUIDs for WebAuthn passwordless authenticators.
    aliases:
      - webAuthnPolicyPasswordlessAcceptableAaguids
    type: list
    version_added: 11.3.0
    elements: str
  web_authn_policy_passwordless_extra_origins:
    description:
      - Additional acceptable origins for WebAuthn passwordless requests.
    aliases:
      - webAuthnPolicyPasswordlessExtraOrigins
    type: list
    version_added: 11.3.0
    elements: str
  web_authn_policy_passwordless_passkeys_enabled:
    description:
      - Enable passkeys (conditional UI) authentication in the username forms.
    aliases:
      - webAuthnPolicyPasswordlessPasskeysEnabled
    type: bool
    version_added: 12.1.0

extends_documentation_fragment:
  - community.general.keycloak
  - community.general.keycloak.actiongroup_keycloak
  - community.general.attributes

author:
  - Christophe Gilles (@kris2kris)
"""

EXAMPLES = r"""
- name: Create or update Keycloak realm (minimal example)
  community.general.keycloak_realm:
    auth_client_id: admin-cli
    auth_keycloak_url: https://auth.example.com/auth
    auth_realm: master
    auth_username: USERNAME
    auth_password: PASSWORD
    realm: unique_realm_name
    state: present

- name: Delete a Keycloak realm
  community.general.keycloak_realm:
    auth_client_id: admin-cli
    auth_keycloak_url: https://auth.example.com/auth
    auth_realm: master
    auth_username: USERNAME
    auth_password: PASSWORD
    realm: unique_realm_name
    state: absent
"""

RETURN = r"""
msg:
  description: Message as to what action was taken.
  returned: always
  type: str
  sample: "Realm testrealm has been updated"

proposed:
  description: Representation of proposed realm.
  returned: always
  type: dict
  sample: {"realm": "test"}

existing:
  description: Representation of existing realm (sample is truncated).
  returned: always
  type: dict
  sample:
    {
      "adminUrl": "http://www.example.com/admin_url",
      "attributes": {
        "request.object.signature.alg": "RS256"
      }
    }

end_state:
  description: Representation of realm after module execution (sample is truncated).
  returned: on success
  type: dict
  sample:
    {
      "adminUrl": "http://www.example.com/admin_url",
      "attributes": {
        "request.object.signature.alg": "RS256"
      }
    }
"""

from ansible_collections.community.general.plugins.module_utils.identity.keycloak.keycloak import (
    KeycloakAPI,
    camel,
    keycloak_argument_spec,
    get_token,
    KeycloakError,
)
from ansible.module_utils.basic import AnsibleModule


def normalise_cr(realmrep):
    """Re-sorts any properties where the order is important so that diff's is minimised and the change detection is more effective.

    :param realmrep: the realmrep dict to be sanitized
    :return: normalised realmrep dict
    """
    # Avoid the dict passed in to be modified
    realmrep = realmrep.copy()

    if "enabledEventTypes" in realmrep:
        realmrep["enabledEventTypes"] = list(sorted(realmrep["enabledEventTypes"]))

    if "otpSupportedApplications" in realmrep:
        realmrep["otpSupportedApplications"] = list(sorted(realmrep["otpSupportedApplications"]))

    if "supportedLocales" in realmrep:
        realmrep["supportedLocales"] = list(sorted(realmrep["supportedLocales"]))

    return realmrep


def sanitize_cr(realmrep):
    """Removes probably sensitive details from a realm representation.

    :param realmrep: the realmrep dict to be sanitized
    :return: sanitized realmrep dict
    """
    result = realmrep.copy()
    if "secret" in result:
        result["secret"] = "********"
    if "attributes" in result:
        if "saml.signing.private.key" in result["attributes"]:
            result["attributes"] = result["attributes"].copy()
            result["attributes"]["saml.signing.private.key"] = "********"
    return normalise_cr(result)


def main():
    """
    Module execution

    :return:
    """
    argument_spec = keycloak_argument_spec()

    meta_args = dict(
        state=dict(default="present", choices=["present", "absent"]),
        id=dict(type="str"),
        realm=dict(type="str"),
        access_code_lifespan=dict(type="int", aliases=["accessCodeLifespan"]),
        access_code_lifespan_login=dict(type="int", aliases=["accessCodeLifespanLogin"]),
        access_code_lifespan_user_action=dict(type="int", aliases=["accessCodeLifespanUserAction"]),
        access_token_lifespan=dict(type="int", aliases=["accessTokenLifespan"], no_log=False),
        access_token_lifespan_for_implicit_flow=dict(
            type="int", aliases=["accessTokenLifespanForImplicitFlow"], no_log=False
        ),
        account_theme=dict(type="str", aliases=["accountTheme"]),
        action_token_generated_by_admin_lifespan=dict(
            type="int", aliases=["actionTokenGeneratedByAdminLifespan"], no_log=False
        ),
        action_token_generated_by_user_lifespan=dict(
            type="int", aliases=["actionTokenGeneratedByUserLifespan"], no_log=False
        ),
        admin_events_details_enabled=dict(type="bool", aliases=["adminEventsDetailsEnabled"]),
        admin_events_enabled=dict(type="bool", aliases=["adminEventsEnabled"]),
        admin_permissions_enabled=dict(type="bool", aliases=["adminPermissionsEnabled"]),
        admin_theme=dict(type="str", aliases=["adminTheme"]),
        attributes=dict(type="dict"),
        browser_flow=dict(type="str", aliases=["browserFlow"]),
        browser_security_headers=dict(type="dict", aliases=["browserSecurityHeaders"]),
        brute_force_protected=dict(type="bool", aliases=["bruteForceProtected"]),
        brute_force_strategy=dict(type="str", choices=["LINEAR", "MULTIPLE"], aliases=["bruteForceStrategy"]),
        client_authentication_flow=dict(type="str", aliases=["clientAuthenticationFlow"]),
        client_scope_mappings=dict(type="dict", aliases=["clientScopeMappings"]),
        default_default_client_scopes=dict(type="list", elements="str", aliases=["defaultDefaultClientScopes"]),
        default_groups=dict(type="list", elements="str", aliases=["defaultGroups"]),
        default_locale=dict(type="str", aliases=["defaultLocale"]),
        default_optional_client_scopes=dict(type="list", elements="str", aliases=["defaultOptionalClientScopes"]),
        default_roles=dict(type="list", elements="str", aliases=["defaultRoles"]),
        default_signature_algorithm=dict(type="str", aliases=["defaultSignatureAlgorithm"]),
        direct_grant_flow=dict(type="str", aliases=["directGrantFlow"]),
        display_name=dict(type="str", aliases=["displayName"]),
        display_name_html=dict(type="str", aliases=["displayNameHtml"]),
        docker_authentication_flow=dict(type="str", aliases=["dockerAuthenticationFlow"]),
        duplicate_emails_allowed=dict(type="bool", aliases=["duplicateEmailsAllowed"]),
        edit_username_allowed=dict(type="bool", aliases=["editUsernameAllowed"]),
        email_theme=dict(type="str", aliases=["emailTheme"]),
        enabled=dict(type="bool"),
        enabled_event_types=dict(type="list", elements="str", aliases=["enabledEventTypes"]),
        events_enabled=dict(type="bool", aliases=["eventsEnabled"]),
        events_expiration=dict(type="int", aliases=["eventsExpiration"]),
        events_listeners=dict(type="list", elements="str", aliases=["eventsListeners"]),
        failure_factor=dict(type="int", aliases=["failureFactor"]),
        internationalization_enabled=dict(type="bool", aliases=["internationalizationEnabled"]),
        login_theme=dict(type="str", aliases=["loginTheme"]),
        login_with_email_allowed=dict(type="bool", aliases=["loginWithEmailAllowed"]),
        max_delta_time_seconds=dict(type="int", aliases=["maxDeltaTimeSeconds"]),
        max_failure_wait_seconds=dict(type="int", aliases=["maxFailureWaitSeconds"]),
        max_temporary_lockouts=dict(type="int", aliases=["maxTemporaryLockouts"]),
        minimum_quick_login_wait_seconds=dict(type="int", aliases=["minimumQuickLoginWaitSeconds"]),
        not_before=dict(type="int", aliases=["notBefore"]),
        offline_session_idle_timeout=dict(type="int", aliases=["offlineSessionIdleTimeout"]),
        offline_session_max_lifespan=dict(type="int", aliases=["offlineSessionMaxLifespan"]),
        offline_session_max_lifespan_enabled=dict(type="bool", aliases=["offlineSessionMaxLifespanEnabled"]),
        otp_policy_algorithm=dict(type="str", aliases=["otpPolicyAlgorithm"]),
        otp_policy_digits=dict(type="int", aliases=["otpPolicyDigits"]),
        otp_policy_initial_counter=dict(type="int", aliases=["otpPolicyInitialCounter"]),
        otp_policy_look_ahead_window=dict(type="int", aliases=["otpPolicyLookAheadWindow"]),
        otp_policy_period=dict(type="int", aliases=["otpPolicyPeriod"]),
        otp_policy_type=dict(type="str", aliases=["otpPolicyType"]),
        otp_supported_applications=dict(type="list", elements="str", aliases=["otpSupportedApplications"]),
        password_policy=dict(type="str", aliases=["passwordPolicy"], no_log=False),
        organizations_enabled=dict(type="bool", aliases=["organizationsEnabled"]),
        permanent_lockout=dict(type="bool", aliases=["permanentLockout"]),
        quick_login_check_milli_seconds=dict(type="int", aliases=["quickLoginCheckMilliSeconds"]),
        refresh_token_max_reuse=dict(type="int", aliases=["refreshTokenMaxReuse"], no_log=False),
        registration_allowed=dict(type="bool", aliases=["registrationAllowed"]),
        registration_email_as_username=dict(type="bool", aliases=["registrationEmailAsUsername"]),
        registration_flow=dict(type="str", aliases=["registrationFlow"]),
        remember_me=dict(type="bool", aliases=["rememberMe"]),
        reset_credentials_flow=dict(type="str", aliases=["resetCredentialsFlow"]),
        reset_password_allowed=dict(type="bool", aliases=["resetPasswordAllowed"], no_log=False),
        revoke_refresh_token=dict(type="bool", aliases=["revokeRefreshToken"]),
        smtp_server=dict(type="dict", aliases=["smtpServer"]),
        ssl_required=dict(choices=["external", "all", "none"], aliases=["sslRequired"]),
        sso_session_idle_timeout=dict(type="int", aliases=["ssoSessionIdleTimeout"]),
        sso_session_idle_timeout_remember_me=dict(type="int", aliases=["ssoSessionIdleTimeoutRememberMe"]),
        sso_session_max_lifespan=dict(type="int", aliases=["ssoSessionMaxLifespan"]),
        sso_session_max_lifespan_remember_me=dict(type="int", aliases=["ssoSessionMaxLifespanRememberMe"]),
        supported_locales=dict(type="list", elements="str", aliases=["supportedLocales"]),
        user_managed_access_allowed=dict(type="bool", aliases=["userManagedAccessAllowed"]),
        verify_email=dict(type="bool", aliases=["verifyEmail"]),
        wait_increment_seconds=dict(type="int", aliases=["waitIncrementSeconds"]),
        client_session_idle_timeout=dict(type="int", aliases=["clientSessionIdleTimeout"]),
        client_session_max_lifespan=dict(type="int", aliases=["clientSessionMaxLifespan"]),
        client_offline_session_idle_timeout=dict(type="int", aliases=["clientOfflineSessionIdleTimeout"]),
        client_offline_session_max_lifespan=dict(type="int", aliases=["clientOfflineSessionMaxLifespan"]),
        oauth2_device_code_lifespan=dict(type="int", aliases=["oauth2DeviceCodeLifespan"]),
        oauth2_device_polling_interval=dict(type="int", aliases=["oauth2DevicePollingInterval"]),
        web_authn_policy_rp_entity_name=dict(type="str", aliases=["webAuthnPolicyRpEntityName"]),
        web_authn_policy_signature_algorithms=dict(
            type="list", elements="str", aliases=["webAuthnPolicySignatureAlgorithms"]
        ),
        web_authn_policy_rp_id=dict(type="str", aliases=["webAuthnPolicyRpId"]),
        web_authn_policy_attestation_conveyance_preference=dict(
            type="str", aliases=["webAuthnPolicyAttestationConveyancePreference"]
        ),
        web_authn_policy_authenticator_attachment=dict(type="str", aliases=["webAuthnPolicyAuthenticatorAttachment"]),
        web_authn_policy_require_resident_key=dict(
            type="str", aliases=["webAuthnPolicyRequireResidentKey"], no_log=False
        ),
        web_authn_policy_user_verification_requirement=dict(
            type="str", aliases=["webAuthnPolicyUserVerificationRequirement"]
        ),
        web_authn_policy_create_timeout=dict(type="int", aliases=["webAuthnPolicyCreateTimeout"]),
        web_authn_policy_avoid_same_authenticator_register=dict(
            type="bool", aliases=["webAuthnPolicyAvoidSameAuthenticatorRegister"]
        ),
        web_authn_policy_acceptable_aaguids=dict(
            type="list", elements="str", aliases=["webAuthnPolicyAcceptableAaguids"]
        ),
        web_authn_policy_extra_origins=dict(type="list", elements="str", aliases=["webAuthnPolicyExtraOrigins"]),
        web_authn_policy_passwordless_rp_entity_name=dict(
            type="str", aliases=["webAuthnPolicyPasswordlessRpEntityName"]
        ),
        web_authn_policy_passwordless_signature_algorithms=dict(
            type="list", elements="str", aliases=["webAuthnPolicyPasswordlessSignatureAlgorithms"], no_log=False
        ),
        web_authn_policy_passwordless_rp_id=dict(type="str", aliases=["webAuthnPolicyPasswordlessRpId"]),
        web_authn_policy_passwordless_attestation_conveyance_preference=dict(
            type="str", aliases=["webAuthnPolicyPasswordlessAttestationConveyancePreference"], no_log=False
        ),
        web_authn_policy_passwordless_authenticator_attachment=dict(
            type="str", aliases=["webAuthnPolicyPasswordlessAuthenticatorAttachment"], no_log=False
        ),
        web_authn_policy_passwordless_require_resident_key=dict(
            type="str", aliases=["webAuthnPolicyPasswordlessRequireResidentKey"], no_log=False
        ),
        web_authn_policy_passwordless_user_verification_requirement=dict(
            type="str", aliases=["webAuthnPolicyPasswordlessUserVerificationRequirement"], no_log=False
        ),
        web_authn_policy_passwordless_create_timeout=dict(
            type="int", aliases=["webAuthnPolicyPasswordlessCreateTimeout"]
        ),
        web_authn_policy_passwordless_avoid_same_authenticator_register=dict(
            type="bool", aliases=["webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister"]
        ),
        web_authn_policy_passwordless_acceptable_aaguids=dict(
            type="list", elements="str", aliases=["webAuthnPolicyPasswordlessAcceptableAaguids"], no_log=False
        ),
        web_authn_policy_passwordless_extra_origins=dict(
            type="list", elements="str", aliases=["webAuthnPolicyPasswordlessExtraOrigins"], no_log=False
        ),
        web_authn_policy_passwordless_passkeys_enabled=dict(
            type="bool", aliases=["webAuthnPolicyPasswordlessPasskeysEnabled"]
        ),
    )

    argument_spec.update(meta_args)

    module = AnsibleModule(
        argument_spec=argument_spec,
        supports_check_mode=True,
        required_one_of=(
            [
                ["id", "realm", "enabled"],
                ["token", "auth_realm", "auth_username", "auth_password", "auth_client_id", "auth_client_secret"],
            ]
        ),
        required_together=([["auth_username", "auth_password"]]),
        required_by={"refresh_token": "auth_realm"},
    )

    result = dict(changed=False, msg="", diff={}, proposed={}, existing={}, end_state={})

    # Obtain access token, initialize API
    try:
        connection_header = get_token(module.params)
    except KeycloakError as e:
        module.fail_json(msg=str(e))

    kc = KeycloakAPI(module, connection_header)

    realm = module.params.get("realm")
    state = module.params.get("state")

    # convert module parameters to realm representation parameters (if they belong in there)
    params_to_ignore = list(keycloak_argument_spec().keys()) + ["state"]

    # Filter and map the parameters names that apply to the role
    realm_params = [x for x in module.params if x not in params_to_ignore and module.params.get(x) is not None]

    # See whether the realm already exists in Keycloak
    before_realm = kc.get_realm_by_id(realm=realm)

    if before_realm is None:
        before_realm = {}

    # Build a proposed changeset from parameters given to this module
    changeset = {}

    for realm_param in realm_params:
        new_param_value = module.params.get(realm_param)
        changeset[camel(realm_param)] = new_param_value

    # Prepare the desired values using the existing values (non-existence results in a dict that is save to use as a basis)
    desired_realm = before_realm.copy()
    desired_realm.update(changeset)

    result["proposed"] = sanitize_cr(changeset)
    before_realm_sanitized = sanitize_cr(before_realm)
    result["existing"] = before_realm_sanitized

    # Cater for when it doesn't exist (an empty dict)
    if not before_realm:
        if state == "absent":
            # Do nothing and exit
            if module._diff:
                result["diff"] = dict(before="", after="")
            result["changed"] = False
            result["end_state"] = {}
            result["msg"] = "Realm does not exist, doing nothing."
            module.exit_json(**result)

        # Process a creation
        result["changed"] = True

        if module._diff:
            result["diff"] = dict(before="", after=sanitize_cr(desired_realm))

        if module.check_mode:
            module.exit_json(**result)

        # create it
        kc.create_realm(desired_realm)
        after_realm = kc.get_realm_by_id(desired_realm["realm"])

        result["end_state"] = sanitize_cr(after_realm)

        result["msg"] = f"Realm {desired_realm['realm']} has been created."
        module.exit_json(**result)

    else:
        if state == "present":
            # Process an update

            # doing an update
            result["changed"] = True
            if module.check_mode:
                # We can only compare the current realm with the proposed updates we have
                before_norm = normalise_cr(before_realm)
                desired_norm = normalise_cr(desired_realm)
                if module._diff:
                    result["diff"] = dict(before=sanitize_cr(before_norm), after=sanitize_cr(desired_norm))
                result["changed"] = before_norm != desired_norm

                module.exit_json(**result)

            # do the update
            kc.update_realm(desired_realm, realm=realm)

            after_realm = kc.get_realm_by_id(realm=realm)

            if before_realm == after_realm:
                result["changed"] = False

            result["end_state"] = sanitize_cr(after_realm)

            if module._diff:
                result["diff"] = dict(before=before_realm_sanitized, after=sanitize_cr(after_realm))

            result["msg"] = f"Realm {desired_realm['realm']} has been updated."
            module.exit_json(**result)

        else:
            # Process a deletion (because state was not 'present')
            result["changed"] = True

            if module._diff:
                result["diff"] = dict(before=before_realm_sanitized, after="")

            if module.check_mode:
                module.exit_json(**result)

            # delete it
            kc.delete_realm(realm=realm)

            result["proposed"] = {}
            result["end_state"] = {}

            result["msg"] = f"Realm {before_realm['realm']} has been deleted."

    module.exit_json(**result)


if __name__ == "__main__":
    main()
